package org.coody.framework.core.util.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.SSLSocketFactory;

import org.coody.framework.core.util.ByteUtils;
import org.coody.framework.core.util.CommonUtil;
import org.coody.framework.core.util.http.entity.HttpEntity;
import org.coody.framework.core.util.string.StringUtil;

public class HttpProcessor {

	static String encode = "UTF-8";

	public static final String GET = "GET";
	public static final String HEAD = "HEAD";
	public static final String POST = "POST";
	public static final String PUT = "PUT";
	public static final String DELETE = "DELETE";
	public static final String OPTIONS = "OPTIONS";

	public static final Integer EXPIRE_MILLISECOND = 15000;

	public static HttpEntity get(String url) throws IOException {
		return http(url, HttpProcessor.GET, null, null, HttpProcessor.EXPIRE_MILLISECOND);
	}

	public static HttpEntity get(String url, Integer expireMillisecond) throws IOException {
		return http(url, HttpProcessor.GET, null, null, expireMillisecond);
	}

	public static HttpEntity get(String url, Map<String, String> header, Integer expireMillisecond) throws IOException {
		return http(url, HttpProcessor.GET, null, header, expireMillisecond);
	}

	public static HttpEntity post(String url, byte[] data) throws IOException {
		return http(url, HttpProcessor.POST, data, null, HttpProcessor.EXPIRE_MILLISECOND);
	}

	public static HttpEntity post(String url, byte[] data, Integer expireMillisecond) throws IOException {
		return http(url, HttpProcessor.POST, data, null, expireMillisecond);
	}

	public static HttpEntity post(String url, byte[] data, Map<String, String> header, Integer expireMillisecond)
			throws IOException {
		return http(url, HttpProcessor.POST, data, header, expireMillisecond);
	}

	public static HttpEntity http(String url, String method, byte[] data, Map<String, String> header,
			Integer expireMillisecond) throws IOException {

		if (header == null) {
			header = new HashMap<String, String>();
		}
//		if (!header.containsKey("Accept")) {
//			header.put("Accept",
//					"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
//		}
//		if (!header.containsKey("User-Agent")) {
//			header.put("User-Agent",
//					"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36");
//		}
//		if (data != null) {
//			header.put("Content-Length", String.valueOf(data.length));
//		}

		StringBuilder headerBuilder = new StringBuilder();

		URL address = new URL(url);

		String host = address.getHost();
		Integer port = (address.getPort() == -1) ? (url.toLowerCase().startsWith("https") ? 443 : 80)
				: address.getPort();

		headerBuilder.append(method).append(" ").append(address.getPath()).append(" HTTP/1.1").append("\r\n");
		headerBuilder.append("Host: ").append(host).append("\r\n");
		for (String key : header.keySet()) {
			headerBuilder.append(key).append(": ").append(header.get(key)).append("\r\n");
		}

		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		stream.write(headerBuilder.toString().getBytes(encode));
		stream.write("\r\n".getBytes(encode));
		if (data != null) {
			stream.write(data);
		}

		data = stream.toByteArray();
		stream.close();
		return socket(host, port, data, expireMillisecond);
	}

	public static HttpEntity socket(String host, Integer port, byte[] data, Integer expireMillisecond)
			throws UnknownHostException, IOException {

		Socket socket = (port != 443) ? new Socket(host, port) : SSLSocketFactory.getDefault().createSocket(host, port);

		socket.setSoTimeout(expireMillisecond);

		socket.getOutputStream().write(data);
		socket.getOutputStream().flush();

		HttpEntity entity = new HttpEntity();
		// 解析响应码
		String line = ByteUtils.readLineString(socket.getInputStream(), encode);
		if (line == null) {
			throw new RuntimeException("请求无响应");
		}
		Integer code = Integer.valueOf(StringUtil.stringCutCenter(line, " ", " "));
		entity.setCode(code);
		// 解析head
		line = ByteUtils.readLineString(socket.getInputStream(), encode);
		while (!CommonUtil.isNullOrEmpty(line.trim())) {
			try {
				String[] attrs = line.split(":");
				if (attrs.length < 2) {
					continue;
				}
				String key = attrs[0].trim();
				String value = line.substring(line.indexOf(":") + 1).trim();
				if (entity.getHeader().containsKey(key)) {
					value = entity.getHeader().get(key) + ";" + value;
				}
				entity.getHeader().put(key, value);
			} finally {
				line = ByteUtils.readLineString(socket.getInputStream(), encode);
			}
		}
		// 判断是否压缩
		String value = entity.getHeader().get("Content-Encoding");
		entity.setGzip((value != null && value.contains("gzip")) ? true : false);
		// 判断是否分段加载
		value = entity.getHeader().get("Transfer-Encoding");
		entity.setChunked((value != null && value.contains("chunked")) ? true : false);
		// 解析内容
		if (!entity.isChunked()) {
			Integer length = Integer.valueOf(entity.getHeader().get("Content-Length"));
			byte[] body = ByteUtils.read(socket.getInputStream(), length);
			entity.setBody(body);
			return entity;
		}
		// 分段解析内容
		String chunk = ByteUtils.readLineString(socket.getInputStream(), encode);
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
			while (chunk != null && !(chunk == null || chunk.trim().length() == 0) && !"0".equals(chunk.trim())) {
				try {
					Integer length = Integer.valueOf(chunk.trim(), 16);
					while (length > 0) {
						byte[] chunked = new byte[length];
						int readed = socket.getInputStream().read(chunked);
						length = length - readed;
						stream.write(chunked, 0, readed);
					}
					chunk = ByteUtils.readLineString(socket.getInputStream(), encode);
					chunk = ByteUtils.readLineString(socket.getInputStream(), encode);
				} catch (Exception e) {
					throw new RuntimeException("读取数据出错", e);
				}
			}
		entity.setBody(stream.toByteArray());
		stream.close();
		return entity;
	}
}
